package com.shuke.medical.insure.component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.shuke.medical.insure.utils.CommonUtil;
import com.tencent.mip.exception.FuncRetCode;
import com.tencent.mip.exception.ServerException;
import com.tencent.mip.util.SM2Util;
import com.tencent.mip.util.SM4Util;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;

/***
 * @title DataHandler
 * @description
 * @author woo hua
 * @version 1.0.0
 * @create 2023/4/3 10:39
 **/
@Slf4j
public class DataHandler {

    private static volatile DataHandler INSTANCE;
    private final byte[] encryptKeys;
    private final String publicKeys;
    private final String privateKeys;
    private final String pubKeys;
    private final String appID;
    private final String secret;
    private String version = "2.0.0";
    private boolean skipVerify = false;
    Base64.Decoder decoder = Base64.getDecoder();
    SM2Util sm2 = new SM2Util();

    private DataHandler(String appID, String secret, String publicKey, String privateKey, String pubKeys) throws IOException {
        this.appID = appID;
        this.secret = secret;
        this.encryptKeys = SM4Util.encryptEcb(CommonUtil.stringToHexString(appID.substring(0, 16)), secret).substring(0, 16).toUpperCase().getBytes();
        this.publicKeys = CommonUtil.byteArrayToHex(this.decoder.decode(publicKey));
        this.privateKeys = CommonUtil.byteArrayToHex(this.decoder.decode(privateKey));
        this.pubKeys = CommonUtil.byteArrayToHex(this.decoder.decode(pubKeys));
    }

    public static DataHandler newInstance(String appID, String secret, String publicKey, String privateKey, String pubKeys) throws IOException {
        if (INSTANCE == null) {
            synchronized (DataHandler.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DataHandler(appID, secret, publicKey, privateKey, pubKeys);
                }
            }
        }
        return INSTANCE;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public void setSkipVerify(boolean skipVerify) {
        this.skipVerify = skipVerify;
    }

    public String buildReqData(JSONObject objData) throws Exception {
        Map<String, Object> map = new TreeMap<>();
        for (Map.Entry<String, Object> entry : objData.entrySet()) {
            map.put(entry.getKey(), entry.getValue());
        }
        String reqData = buildReqData(map);
        return reqData;
    }

    public String buildReqData(Object objData) throws Exception {
        Map<String, Object> map = CommonUtil.objectToMap(objData);
        log.info("buildReqData参数转换map结果：" + map);
        String reqData = buildReqData(map);
        return reqData;
    }

    public String buildReqData(Map<String, Object> mapData) throws Exception {
        String str = JSON.toJSONString(mapData);
        JSONObject jsonObject = JSONObject.parseObject(str);
        String jsonStr = CommonUtil.valueToString(jsonObject);
        JSONObject parseObject = JSON.parseObject(jsonStr);
        Map<String, Object> treeMap = new TreeMap<>();
        treeMap.put("version", this.version);
        treeMap.put("encType", SM4Util.ALGORITHM_NAME);
        treeMap.put("signType", "SM2");
        treeMap.put("appId", this.appID);
        treeMap.put("timestamp", System.currentTimeMillis());
        treeMap.put("extra", null);
        treeMap.put("data", JSON.toJSON(parseObject));
        String signStr = CommonUtil.getSignStr(this.secret, treeMap);
        String signData = this.sm2.sign(signStr, this.privateKeys);
        treeMap.put("signData", signData);
        log.info("签名结果：" + signData);
        byte[] dataBytes = treeMap.get("data").toString().getBytes(StandardCharsets.UTF_8);
        byte[] encDataBytes = SM4Util.encrypt_Ecb_Padding(this.encryptKeys, dataBytes);
        String encData = ByteUtils.toHexString(encDataBytes);
        treeMap.put("encData", encData);
        log.info("加密结果：" + encData);
        treeMap.remove("data");
        return JSON.toJSONString(treeMap);
    }

    public String processRspData(String rspData) throws Exception {
        Map<String, Object> treeMap = (Map) JSON.parseObject(rspData, new TreeMap<>().getClass());
        Integer codeObj = (Integer) treeMap.get("code");
        if (codeObj != null && codeObj.intValue() == 0) {
            String encData = treeMap.get("encData").toString();
            try {
                byte[] cipherData = ByteUtils.fromHexString(encData);
                byte[] cipherByte = SM4Util.decrypt_Ecb_Padding(this.encryptKeys, cipherData);
                String dEncData = new String(cipherByte, StandardCharsets.UTF_8);
                log.info("解密结果: " + dEncData);
                JSONObject jsonObject = JSONObject.parseObject(dEncData);
                String jsonStr = CommonUtil.valueToString(jsonObject);
                Map<String, Object> dataMap = new TreeMap<>();
                treeMap.put("data", (Map) JSON.parseObject(jsonStr, dataMap.getClass()));
                String signData = treeMap.get("signData").toString();
                if (!this.skipVerify) {
                    String signStr = CommonUtil.getSignStr(this.secret, treeMap);
                    if (!this.sm2.verify(signStr, signData, this.pubKeys)) {
                        log.error("sm2 对返回数据校验结果失败：[{}]， 验签数据：signStr: [{}]， signData: [{}], ",
                                FuncRetCode.ERROR_CODE_SIGN_ERROR.getMessage(), signStr, signData);
                        throw new ServerException(FuncRetCode.ERROR_CODE_SIGN_ERROR);
                    }else {
                        log.info("sm2 对返回数据校验结果成功");
                    }
                }
                treeMap.remove("encData");
                treeMap.remove("signData");

            } catch (Exception e) {
                throw new ServerException(FuncRetCode.ERROR_CODE_ENCRYPT_ERROR);
            }
        }
        return JSON.toJSONString(treeMap);
    }

}
